package com.trilogy.fs.raptor.clustering;

import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JRadioButton;
import javax.swing.JTextField;
import javax.swing.JSlider;
import javax.swing.BoxLayout;
import javax.swing.BorderFactory;
import javax.swing.SwingUtilities;
import javax.swing.event.ChangeListener;
import javax.swing.event.ChangeEvent;
import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.FlowLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.util.List;
import java.util.ArrayList;
import java.lang.reflect.InvocationTargetException;

/**
 * @author <a href="razvan.surdulescu@post.harvard.edu">Razvan Surdulescu</a>, Copyright (c) 2003
 */
public class DotFrame extends JFrame {
    private DotCanvas m_canvas;
    private JLabel m_statusPointLabel;
    private JLabel m_statusExecutionLabel;

    private String m_algorithm = ClusteringAlgorithm.DEFAULT_ALGORITHM;
    private double m_radius = ClusteringAlgorithm.DEFAULT_SPATIAL_RADIUS;

    private boolean m_busy;

    public DotFrame(String title) {
        super(title);

        addWindowListener(new WindowAdapter() {
            public void windowClosing(WindowEvent e) {
                System.exit(0);
            }
        });

        Container container = getContentPane();
        container.setLayout(new BorderLayout());

        m_canvas = new DotCanvas(this);
        container.add(m_canvas, BorderLayout.CENTER);

        JPanel controlPanel = new JPanel();
        controlPanel.setLayout(new BoxLayout(controlPanel, BoxLayout.Y_AXIS));

        controlPanel.add(createGuiSubPanel());
        controlPanel.add(createDataSubPanel());
        controlPanel.add(createExecutionSubPanel());
        controlPanel.add(createStatusSubPanel());

        container.add(controlPanel, BorderLayout.SOUTH);
    }

    private JPanel createStatusSubPanel() {
        JPanel statusSubPanel = new JPanel();
        statusSubPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        statusSubPanel.setBorder(BorderFactory.createTitledBorder("Status"));

        m_statusPointLabel = new JLabel();
        m_statusExecutionLabel = new JLabel();

        statusSubPanel.add(m_statusPointLabel);
        statusSubPanel.add(m_statusExecutionLabel);

        return statusSubPanel;
    }

    private JPanel createExecutionSubPanel() {
        JPanel executionSubPanel = new JPanel();
        executionSubPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        executionSubPanel.setBorder(BorderFactory.createTitledBorder("Execution Controls"));

        JPanel executionAlgorithmPanel = new JPanel();
        executionAlgorithmPanel.setLayout(new BoxLayout(executionAlgorithmPanel, BoxLayout.Y_AXIS));

        JRadioButton executionAlgorithmLanczos = new JRadioButton("Lanczos");
        executionAlgorithmLanczos.setSelected(true);
        executionAlgorithmLanczos.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                m_algorithm = "Lanczos";
            }
        });

        JRadioButton executionAlgorithmQL = new JRadioButton("QL");
        executionAlgorithmQL.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                m_algorithm = "QL";
            }
        });

        ButtonGroup executionAlgorithmGroup = new ButtonGroup();
        executionAlgorithmGroup.add(executionAlgorithmLanczos);
        executionAlgorithmGroup.add(executionAlgorithmQL);

        executionAlgorithmPanel.add(new JLabel("Algorithm:"));
        executionAlgorithmPanel.add(executionAlgorithmLanczos);
        executionAlgorithmPanel.add(executionAlgorithmQL);

        JPanel executionRadiusPanel = new JPanel();
        executionRadiusPanel.setLayout(new BoxLayout(executionRadiusPanel, BoxLayout.Y_AXIS));

        JSlider executionRadiusSlider = new JSlider(JSlider.HORIZONTAL, 10, 90, (int)m_radius);
        executionRadiusSlider.setMajorTickSpacing(10);
        executionRadiusSlider.setMinorTickSpacing(5);
        executionRadiusSlider.setPaintTicks(true);
        executionRadiusSlider.setPaintLabels(true);
        executionRadiusSlider.addChangeListener(new ChangeListener() {
            public void stateChanged(ChangeEvent e) {
                JSlider slider = (JSlider) e.getSource();
                if (!slider.getValueIsAdjusting()) {
                    m_radius = slider.getValue();
                    if (m_radius == 0) {
                        m_radius = 1;
                    }
                }
            }
        });

        executionRadiusPanel.add(new JLabel("Spatial Radius:"));
        executionRadiusPanel.add(executionRadiusSlider);

        JButton executionClusterButton = new JButton("Cluster");
        executionClusterButton.setToolTipText("Compute the clusters");

        executionClusterButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                if (m_busy) {
                    JOptionPane.showMessageDialog(DotFrame.this,
                            "Clustering already in progress. Please wait!", "Busy",
                            JOptionPane.OK_OPTION | JOptionPane.ERROR_MESSAGE);
                    return;
                } else {
                    m_busy = true;
                }

                final ClusteringAlgorithm algorithm = new ClusteringAlgorithm();
                algorithm.setAlgorithm(m_algorithm);
                algorithm.setRadius(m_radius);

                final List in = new ArrayList();
                in.add(m_canvas.copyDots());

                final List out = new ArrayList();

                Thread worker = new Thread() {
                    private boolean execute(Runnable job) {
                        try {
                            SwingUtilities.invokeAndWait(job);
                            return true;
                        } catch (Exception ex) {
                            Throwable t = ex;
                            if (ex instanceof InvocationTargetException) {
                                t = ((InvocationTargetException)ex).getTargetException();
                            }

                            JOptionPane.showMessageDialog(DotFrame.this,
                                    "Failure during clustering: " + t.getMessage(), "Clustering failure",
                                    JOptionPane.OK_OPTION | JOptionPane.ERROR_MESSAGE);
                            return false;
                        }
                    }

                    public void run() {
                        final long start = System.currentTimeMillis();

                        int count = 0;
                        while(!in.isEmpty()) {
                            final int _count = count;
                            execute(new Runnable() {
                                public void run() {
                                    m_statusExecutionLabel.setText("Clustering iteration #" + _count + " ...");
                                }
                            });

                            List parent = (List) in.remove(0);
                            algorithm.split(parent);

                            List cluster1 = algorithm.getCluster1();
                            List cluster2 = algorithm.getCluster2();

                            if (algorithm.ncut(cluster1, cluster2, parent) > 0) {
                                out.add(parent);
                            } else {
                                in.add(cluster1);
                                in.add(cluster2);
                            }

                            count++;
                        }

                        final long end = System.currentTimeMillis();

                        execute(new Runnable() {
                            public void run() {
                                m_statusExecutionLabel.setText("Clusters: " + out.size() + " Elapsed: " + (end - start) + " ms");
                                m_canvas.setClusters(out);
                            }
                        });

                        m_busy = false;
                    }
                };

                worker.start();
            }
        });

        executionSubPanel.add(executionAlgorithmPanel);
        executionSubPanel.add(executionRadiusPanel);
        executionSubPanel.add(executionClusterButton);
        return executionSubPanel;
    }

    private JPanel createDataSubPanel() {
        JPanel dataSubPanel = new JPanel();
        dataSubPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        dataSubPanel.setBorder(BorderFactory.createTitledBorder("Data Controls"));

        final JTextField dataClustersField = new JTextField(5);
        dataClustersField.setToolTipText("Number of random clusters to generate");

        final JTextField dataPointsField = new JTextField(5);
        dataPointsField.setToolTipText("Number of random points to generate");

        JButton dataGenerateButton = new JButton("Generate");
        dataGenerateButton.setToolTipText("Generate random clusters of points");
        dataGenerateButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                try {
                    int clusterCount = Integer.parseInt(dataClustersField.getText());
                    int pointCount = Integer.parseInt(dataPointsField.getText());
                    if (clusterCount <= 0 || pointCount <= 0) {
                        JOptionPane.showMessageDialog(DotFrame.this, "Invalid cluster or point count", "Input error",
                                JOptionPane.OK_OPTION | JOptionPane.ERROR_MESSAGE);
                    } else {
                        m_canvas.generate(clusterCount, pointCount);
                    }
                } catch (NumberFormatException nfe) {
                    JOptionPane.showMessageDialog(DotFrame.this, "Invalid cluster or point count", "Parse error",
                            JOptionPane.OK_OPTION | JOptionPane.ERROR_MESSAGE);
                }
            }
        });

        dataSubPanel.add(new JLabel("Clusters:"));
        dataSubPanel.add(dataClustersField);
        dataSubPanel.add(new JLabel("Points:"));
        dataSubPanel.add(dataPointsField);
        dataSubPanel.add(dataGenerateButton);
        return dataSubPanel;
    }

    private JPanel createGuiSubPanel() {
        JPanel guiSubPanel = new JPanel();
        guiSubPanel.setLayout(new FlowLayout(FlowLayout.LEFT));
        guiSubPanel.setBorder(BorderFactory.createTitledBorder("GUI Controls"));

        JButton guiClearButton = new JButton("Clear");
        guiClearButton.setToolTipText("Clear the canvas");
        guiClearButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                m_canvas.clear();
            }
        });

        JButton guiRefreshButton = new JButton("Refresh");
        guiRefreshButton.setToolTipText("Refresh the canvas");
        guiRefreshButton.addActionListener(new ActionListener() {
            public void actionPerformed(ActionEvent e) {
                m_canvas.repaint();
            }
        });

        guiSubPanel.add(guiClearButton);
        guiSubPanel.add(guiRefreshButton);
        return guiSubPanel;
    }

    public void updatePointCount(int count) {
        m_statusPointLabel.setText("Points: " + count);
    }
}
